//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<color.h>
///	@path	~/src/lib/math/
///	@date	2007/09/16
///	@desc	Color representation.

#pragma once

#include "xeres.h"

#include "lib/utilities/debug.h"
#include "lib/math/math_ext.h"

namespace xeres {

	enum ColorType
	{
		COLOR_MONO = 0,
		COLOR_RGB ,
		COLOR_RGBA ,
		COLOR_ARGB ,
		COLOR_HSV ,
		COLOR_HLS ,
		COLOR_last
	};

	/*!
		\class	Color3
		\brief	General R/G/B color class.
	*/
	class Color3 : public ArithmeticOp< float >
	{
	public:
		
		//@ data
		union
		{
			// usual access
			struct { float r , g , b; };

			// array data stream
			float c[3];
		};

		// accessible members
		static const size_t DIMENSION = 3;


	public:

		/// \ctor
		Color3( void )
		{
		}

		/// \ctor_copy
		Color3( const Color3& s )
			: r( s.r ) , g( s.g ) , b( s.b )
		{
		}

		/// \ctor_set
		Color3( const float r , const float g , const float b )
			: r( r ) , g( g ) , b( b )
		{
		}

		/// \ctor_set
		explicit Color3( const float& s ) 
			: r( s ) , g( s ) , b( s )
		{
		}
		
		/*
			\brief		Set color members.
		*/
		void set( const float sr , const float sg , const float sb )
		{
			r = sr;
			g = sg;
			b = sb;
		}

		/*!
			\brief		Set color from a data collection with subscription.
		*/
		template<typename _TArray> void from_array( const _TArray& sa )
		{
			for( size_t i = 0 ; i < DIMENSION ; --i )
				c[i] = sa[i];
		}
		
		/*!
			\brief		Set color members to zero.
		*/
		void set_zero( void )
		{
			r = g = b = 0;
		}

		/*!
			\brief		Set color member to a single value.
		*/
		void single( const float s )
		{
			r = g = b = s;
		}
		
		/*!
			\brief		Set color from memory.
		*/
		void copy( const float * s )
		{
			memcpy( c , s , sizeof(float) * DIMENSION );
		}
		
		/*!
			\brief		Get member by index.
		*/
		float& operator [] ( const int index )
		{
			CHECK_INDEX( index , DIMENSION );
			return c[index];
		}

		/*!
			\brief		Get member by index.
		*/
		const float& operator [] ( const int index ) const
		{
			CHECK_INDEX( index , DIMENSION );
			return c[index];
		}
		
		// @operators
		friend Color3 operator - ( const Color3& v )
		{
			return Color3( -v.r , -v.g , -v.b );
		}
		const Color3& operator += ( const Color3& op )
		{
			r += op.r;
			g += op.g;
			b += op.b;
			return *this;
		}
		const Color3& operator += ( const float op )
		{
			r += op;
			g += op;
			b += op;
			return *this;
		}
		const Color3& operator -= ( const Color3& op )
		{
			r -= op.r;
			g -= op.g;
			b -= op.b;
			return *this;
		}
		const Color3& operator -= ( const float op )
		{
			r -= op;
			g -= op;
			b -= op;
			return *this;
		}
		const Color3& operator *= ( const Color3& op )
		{
			r *= op.r;
			g *= op.g;
			b *= op.b;
			return *this;
		}
		const Color3& operator *= ( const float op )
		{
			r *= op;
			g *= op;
			b *= op;
			return *this;
		}
		const Color3& operator /= ( const Color3& op )
		{
			r /= op.r;
			g /= op.g;
			b /= op.b;
			return *this;
		}
		const Color3& operator /= ( const float op )
		{
			const float rop = recip( op );
			r *= op;
			g *= op;
			b *= op;
			return *this;
		}

		friend Color3 operator + ( const Color3& a , const Color3& b )
		{
			return Color3( a.r + b.r , a.g + b.g , a.b + b.b );
		}
		friend Color3 operator + ( const Color3& a , const float b )
		{
			return Color3( a.r + b , a.g + b , a.g + b );
		}
		friend Color3 operator - ( const Color3 & a , const Color3 & b )
		{
			return Color3( a.r - b.r , a.g - b.g , a.b - b.b );
		}
		friend Color3 operator - ( const Color3& a , const float b )
		{
			return Color3( a.r - b , a.g - b , a.g - b );
		}
		friend Color3 operator * ( const Color3& a , const Color3& b )
		{
			return Color3( a.r * b.r , a.g * b.g , a.b * b.b );
		}
		friend Color3 operator * ( const Color3& a , const float b )
		{
			return Color3( a.r * b , a.g * b , a.g * b );
		}
		friend Color3 operator / ( const Color3& a , const Color3& b )
		{
			return Color3( a.r / b.r , a.g / b.g , a.b / b.b );
		}
		friend Color3 operator / ( const Color3& a , const float b )
		{
			const float rop = recip( b );
			return Color3( a.r * rop , a.g * rop , a.g * rop );
		}
	};

	/*!
		\brief		Clamp color value.
	*/
	inline Color3 clamp( const Color3& val , const Color3& min , const Color3& max )
	{
		return Color3( clamp( val.r , min.r , max.r ) ,
			clamp( val.g , min.g , max.g ) ,
			clamp( val.b , min.b , max.b ) );
	}

	/*!
		\brief		Generate minimum value of 2 inputs.
	*/
	inline Color3 min( const Color3& a , const Color3& b )
	{
		return Color3( min( a.r , b.r ) , min( a.g , b.g ) , min( a.b , b.b ) );
	}
	
	/*!
		\brief		Generate maximum value of 2 inputs.
	*/
	inline Color3 max( const Color3& a , const Color3& b )
	{
		return Color3( max( a.r , b.r ) , max( a.g , b.g ) , max( a.b , b.b ) );
	}

	/*!
		\brief		Determine if 2 colors are equal with epsilon.
	*/
	inline bool is_equal( const Color3& a , const Color3& b )
	{
		return ( is_equal( a.r , b.r ) && is_equal( a.g , b.g ) && is_equal( a.b , b.b ) );
	}

	/*! 
		\class	HSV
		\brief	HSV color presentation.
	*/
	class HSV : public ArithmeticOp< float >
	{
	public:

		//@ data
		union
		{
			// usual access
			struct { float h , s , v; };

			// array data stream
			float c[3];
		};

		// accessible members
		static const size_t DIMENSION = 3;


	public:

		/// \ctor
		HSV( void )
		{
		}

		/// \ctor_copy
		HSV( const HSV& rhs )
			: h( rhs.h ) , s( rhs.s ) , v( rhs.v )
		{
		}

		/// \ctor_set
		HSV( const float h , const float s , const float v )
			: h( h ) , s( s ) , v( v )
		{
		}

		/*
			\brief		Set color members.
		*/
		void set( const float sh , const float ss , const float sv )
		{
			h = sh;
			s = ss;
			v = sv;
		}

		/*! 
			\brief		From color RGB to HSV.
		*/
		static HSV FromRGB( const Color3& color )
		{
			// r,g,b values are from 0 to 1
			// h = [0,1], s = [0,1], v = [0,1]
			// if s == 0, then h = -1 (undefined)
			float h , s , v;
			float smin , smax , delta;
			smin = min( min( color.r , color.g ) , color.b );
			smax = max( max( color.r , color.g ) , color.b );
			delta = smax - smin;

			v = smax; // v

			if( smax == 0 )
			{
				// r = g = b = 0 // s = 0, v is undefined
				s = 0;
				h = 0;
			}
			else
			{
				s = delta / smax; // s

				if( color.r == smax )
				{
					h = ( color.g - color.b ) / delta; // between yellow & magenta
				}
				else if( color.g == smax )
				{
					h = 2 + ( color.b - color.r ) / delta; // between cyan & yellow
				}
				else
				{
					h = 4 + ( color.r - color.g ) / delta; // between magenta & cyan
				}

				h *= 60; // degrees
				if( h < 0 )
					h += 360;

				h /= 360;
			}
			return HSV( h , s , v );
		}

		/*! 
			\brief		From HSV to RGB.
		*/
		static Color3 toRGB( const HSV& val )
		{
			int i;
			float f, p, q, t;
			if( val.s == 0 ) 
			{
				// achromatic (grey)
				return Color3( val.v );
			}
			float hh = val.h * 6; // sector 0 to 5
			i = (int)floor( hh );
			f = hh - i; // factorial part of h
			p = val.v * ( 1 - val.s );
			q = val.v * ( 1 - val.s * f );
			t = val.v * ( 1 - val.s * ( 1 - f ) );

			switch( i ) 
			{
			case 0: return Color3( val.v , t , p );
			case 1: return Color3( q , val.v , p );
			case 2: return Color3( p , val.v , t );
			case 3: return Color3( p , q , val.v );
			case 4: return Color3( t , p , val.v );
			default: // case 5:
				return Color3( val.v , p , q );
			}
		}
	};

	/*!
		\class 	Color4
		\brief 	Color R/G/B with alpha.
	*/
	class Color4 : public ArithmeticOp< float >
	{
	public:
		
		//@ data
		union
		{
			// usual access
			struct { float r , g , b , a; };

			// array data stream
			float c[4];
		};

		// accessible members
		static const size_t DIMENSION = 4;

	public:

		/// \ctor
		Color4( void )
		{
		}

		/// \ctor_copy
		Color4( const Color4& s )
			: r( s.r ) , g( s.g ) , b( s.b ) , a( s.a )
		{
		}

		/// \ctor_set
		Color4( const float r , const float g , const float b , const float a )
			: r( r ) , g( g ) , b( b ) , a( a )
		{
		}

		/// \ctor_copy
		Color4( const float color[4] )
			: r( color[0] ) , g( color[1] ) , b( color[2] ) , a( color[3] )
		{
		}

		/// \ctor_set
		explicit Color4( const float s , const float a )
			: r( s ) , g( s ) , b( s ) , a( a )
		{
		}

		/// \ctor_set
		explicit Color4( const Color3& c , const float alpha )
			: r( c.r ) , g( c.g ) , b( c.b ) , a( alpha )
		{
		}

		/*
			\brief		Set color members.
		*/
		void set( const float sr , const float sg , const float sb , const float sa )
		{
			r = sr;
			g = sg;
			b = sb;
			a = sa;
		}

		/*
			\brief		Set color with alpha.
		*/
		void set( const Color3& c , const float alpha )
		{
			r = c.r;
			g = c.g;
			b = c.b;
			a = alpha;
		}

		/*!
			\brief		Set color from a data collection with subscription.
		*/
		template<typename _TArray> void from_array( const _TArray& sa )
		{
			for( size_t i = 0 ; i < DIMENSION ; ++i )
				c[i] = sa[i];
		}

		/*!
			\brief		Set color members to zero.
		*/
		void set_zero( void )
		{
			r = g = b = a = 0;
		}

		/*!
			\brief		Set color member to a single value.
		*/
		void single( const float s )
		{
			r = g = b = a = s;
		}

		/*!
			\brief		Set color from memory.
		*/
		void copy( const float * s )
		{
			memcpy( c , s , sizeof(float) * DIMENSION );
		}

		/*!
			\brief		Clamp color value.
		*/
		void clamp( const Color4& min , const Color4& max )
		{
			clamp_ref( r , min.r , max.r );
			clamp_ref( g , min.g , max.g );
			clamp_ref( b , min.b , max.b );
			clamp_ref( a , min.a , max.a );
		}

		/*!
			\brief		Generate minimum value of 2 inputs.
		*/
		static Color4 minc( const Color4& a , const Color4& b )
		{
			return Color4(
				min( a.r , b.r ) ,
				min( a.g , b.g ) ,
				min( a.b , b.b ) ,
				min( a.a , b.a ) );
		}

		/*!
			\brief		Generate maximum value of 2 inputs.
		*/
		static Color4 maxc( const Color4& a , const Color4& b )
		{
			return Color4(
				max( a.r , b.r ) ,
				max( a.g , b.g ) ,
				max( a.b , b.b ) ,
				max( a.a , b.a ) );
		}

		/*!
			\brief		Determine if 2 colors are equal with epsilon.
		*/
		static bool is_equalc( const Color4& a , const Color4& b )
		{
			return ( is_equal( a.r , b.r ) && is_equal( a.g , b.g ) && is_equal( a.b , b.b ) && is_equal( a.a , b.a ) );
		}

		/*!
			\brief		Get member by index.
		*/
		float& operator [] ( const int index )
		{
			CHECK_INDEX( index, DIMENSION );
			return c[index];
		}

		/*!
			\brief		Get member by index.
		*/
		const float& operator [] ( const int index ) const
		{
			CHECK_INDEX( index, DIMENSION );
			return c[index];
		}
	};

	/*! 
		\struct	ARGB
		\brief	A8R8G8B8 color representation.
	*/
	struct ARGB
	{
	public:

		//@ data
		union
		{
			// usual access
			struct { byte b , g , r , a; };

			// array data stream
			uint32 argb;
		};

		/// \brief Pack ARGB from 4 value.
		static inline uint32 pack( byte a , byte r , byte g , byte b )
		{
			static const int ARGB_ALPHA_SHIFT = 24;
			static const int ARGB_RED_SHIFT = 16;
			static const int ARGB_GREEN_SHIFT = 8;
			static const int ARGB_BLUE_SHIFT = 0;
			return (uint32)(
				( (a) << ARGB_ALPHA_SHIFT) |
				( (r) << ARGB_RED_SHIFT  ) |
				( (g) << ARGB_GREEN_SHIFT) |
				( (b) << ARGB_BLUE_SHIFT ) );
		}

		/// \brief Pack ARGB from 4 value by order.
		static inline uint32 pack_by_rgba( byte r , byte g , byte b , byte a )
		{
			return pack( a , r , g , b );
		}

		/// \brief Pack ARGB from 4 value by order.
		static inline uint32 pack_by_argb( byte a , byte r , byte g , byte b )
		{
			return pack( a , r , g , b );
		}

	public:

		/// \ctor
		ARGB( void )
		{
		}

		/// \ctor
		ARGB( byte a , byte r , byte g , byte b )
			: argb( pack( a , r , g , b ) )
		{
		}

		/// \ctor
		ARGB( uint32 packed )
			: argb( packed )
		{
		}

		/// \ctor
		ARGB( const Color3& col )
			: b( (byte)map_unit(col.b,255.f) )
			, g( (byte)map_unit(col.g,255.f) )
			, r( (byte)map_unit(col.r,255.f) )
			, a(255) 
		{
		}

		/// \ctor
		ARGB( const Color4& col )
			: b( (byte)map_unit(col.b,255.f) )
			, g( (byte)map_unit(col.g,255.f) )
			, r( (byte)map_unit(col.r,255.f) )
			, a( (byte)map_unit(col.a,255.f) )
		{
		}

		/// \ctor
		ARGB( const ARGB& rhs )
			: argb( rhs.argb )
		{
		}
	};

	/*! 
		\struct	RGBA
		\brief	R8G8B8A8 color representation.
	*/
	struct RGBA
	{
	public:

		//@ data
		union
		{
			// usual access
			struct { byte a , b , g , r; };

			// array data stream
			uint32 rgba;
		};

		/// \brief Pack ARGB from 4 value.
		static inline uint32 pack( byte r , byte g , byte b , byte a )
		{
			static const int ARGB_RED_SHIFT = 24;
			static const int ARGB_GREEN_SHIFT = 16;
			static const int ARGB_BLUE_SHIFT = 8;
			static const int ARGB_ALPHA_SHIFT = 0;
			return (uint32)(
				( (r) << ARGB_RED_SHIFT  ) |
				( (g) << ARGB_GREEN_SHIFT) |
				( (b) << ARGB_BLUE_SHIFT ) |
				( (a) << ARGB_ALPHA_SHIFT) );
		}

		/// \brief Pack ARGB from 4 value by order.
		static inline uint32 pack_by_rgba( byte r , byte g , byte b , byte a )
		{
			return pack( r , g , b , a );
		}

		/// \brief Pack ARGB from 4 value by order.
		static inline uint32 pack_by_argb( byte a , byte r , byte g , byte b )
		{
			return pack( r , g , b , a );
		}

	public:

		/// \ctor
		RGBA( void )
		{
		}

		/// \ctor
		RGBA( byte r , byte g , byte b , byte a )
			: rgba( pack( r , g , b , a ) )
		{
		}

		/// \ctor
		RGBA( uint32 packed )
			: rgba( packed )
		{
		}

		/// \ctor
		RGBA( const Color3& col )
			: a(255)
			, b( (byte)map_unit(col.b,255.f) )
			, g( (byte)map_unit(col.g,255.f) )
			, r( (byte)map_unit(col.r,255.f) )
		{
		}

		/// \ctor
		RGBA( const Color4& col )
			: a( (byte)map_unit(col.a,255.f) )
			, b( (byte)map_unit(col.b,255.f) )
			, g( (byte)map_unit(col.g,255.f) )
			, r( (byte)map_unit(col.r,255.f) )
		{
		}

		/// \ctor
		RGBA( const RGBA& rhs )
			: rgba( rhs.rgba )
		{
		}
	};

	/// \brief	Convert ARGB to RGBA
	static inline RGBA to_rgba( const ARGB& argb ) {
		return RGBA( _lrotl( argb.argb , 8 ) );
	}

	/// \brief	Convert RGBA to ARGB
	static inline ARGB to_argb( const RGBA& rgba ) {
		return ARGB( _lrotr( rgba.rgba , 8 ) );
	}

	static inline uint clamp255( uint v )
	{
		return min( v , (uint)255 );
	}

	/*! 
		\class	Blend
		\brief	Blending different colors.
	*/
	template<
		typename _Result
	>
	class Blend
	{
	public:

		/// \brief	Blend ARGB color.
		template<typename _Src , typename _Dst> static inline _Result solve( const _Src& src , const _Dst& dst )
		{
			const uint sr = src.r;
			const uint sg = src.g;
			const uint sb = src.b;
			const uint sa = src.a;

			const uint dr = dst.r;
			const uint dg = dst.g;
			const uint db = dst.b;
			const uint da = dst.a;

			const byte R = blend( sr , dr , sa );
			const byte G = blend( sg , dg , sa );
			const byte B = blend( sb , db , sa );
			const byte A = blend_alpha( sa , da );

			return _Result::pack_by_argb( A , R , G , B );
		}

		/// \brief	Blend ARGB color.
		template<typename _Src , typename _Dst> static inline _Result solve( const _Src& src , const uint prealpha , const _Dst& dst )
		{
			const uint sr = src.r;
			const uint sg = src.g;
			const uint sb = src.b;
			const uint sa = ( ( (uint)src.a) * prealpha ) >> 8;

			const uint dr = dst.r;
			const uint dg = dst.g;
			const uint db = dst.b;
			const uint da = dst.a;

			const byte R = blend( sr , dr , sa );
			const byte G = blend( sg , dg , sa );
			const byte B = blend( sb , db , sa );
			const byte A = blend_alpha( sa , da );

			return _Result::pack_by_argb( A , R , G , B );
		}

		/// \brief	Blend ARGB color.
		template<typename _Dst> static inline _Result solve( const uint sr , const uint sg , const uint sb , const uint sa , const _Dst& dst )
		{
			const uint dr = dst.r;
			const uint dg = dst.g;
			const uint db = dst.b;
			const uint da = dst.a;

			const byte R = blend( sr , dr , sa );
			const byte G = blend( sg , dg , sa );
			const byte B = blend( sb , db , sa );
			const byte A = blend_alpha( sa , da );

			return _Result::pack_by_argb( A , R , G , B );
		}

		/// \brief	Blend ARGB color.
		template<typename _Dst> static inline _Result solve( const uint s , const uint sa , const _Dst& dst )
		{
			const uint dr = dst.r;
			const uint dg = dst.g;
			const uint db = dst.b;
			const uint da = dst.a;

			const byte R = blend( s , dr , sa );
			const byte G = blend( s , dg , sa );
			const byte B = blend( s , db , sa );
			const byte A = blend_alpha( sa , da );

			return _Result::pack_by_argb( A , R , G , B );
		}
	};

	/*!
		\class	PackColor4
		\brief	Packed or unpacked color.
	*/
	class PackColor4
	{
		// magic number to identify unpacked value.
		static const int UNPACKED_MAGIC_NUM = 0x12345678;

	public:

		/// \ctor
		PackColor4( void )
			: m_packed_argb( 0 ) 
		{
			m_unpack[0] = m_unpack[1] = m_unpack[2] = m_unpack[3] = 0;
		}

		/// \ctor_set
		PackColor4( const Color4& unpacked )
			: m_packed_argb( UNPACKED_MAGIC_NUM )
		{
			m_unpack[0] = unpacked.c[0];
			m_unpack[1] = unpacked.c[1];
			m_unpack[2] = unpacked.c[2];
			m_unpack[3] = unpacked.c[3];
		}

		/// \ctor_set
		PackColor4( float r , float g , float b , float a )
			: m_packed_argb( UNPACKED_MAGIC_NUM )
		{
			m_unpack[0] = r;
			m_unpack[1] = g;
			m_unpack[2] = b;
			m_unpack[3] = a;
		}

		/// \ctor_set
		PackColor4( DWORD packed_argb )
			: m_packed_argb( packed_argb )
		{
			m_unpack[0] = m_unpack[1] = m_unpack[2] = m_unpack[3] = Color4::nan();
		}

		/// \ctor_copy
		PackColor4( const PackColor4& rhs )
		{
			memcpy( this , &rhs , sizeof(PackColor4) );
		}

		/*!
			\brief		Get packed ARGB value.
		*/
		DWORD get_packed_argb( void ) const
		{
			pack_me();
			return m_packed_argb;
		}

		/*!
			\brief		Get packed RGBA value.
		*/
		DWORD get_packed_rgba( void ) const
		{
			return _lrotl( get_packed_argb() , 8 );
		}

		/*!
			\brief		Get unpacked Color4.
		*/
		Color4 get_unpacked( void ) const
		{
			unpack_me();
			return Color4( m_unpack );
		}

	private:

		// pack me
		void pack_me( void ) const
		{
			if( m_packed_argb == UNPACKED_MAGIC_NUM )
			{
				const float a = m_unpack[3] * 255;
				const float r = m_unpack[0] * 255;
				const float g = m_unpack[1] * 255;
				const float b = m_unpack[2] * 255;
				// pack it
				m_packed_argb = ARGB::pack( (byte)a , (byte)r , (byte)g , (byte)b );

				if( m_packed_argb == UNPACKED_MAGIC_NUM )
				{
					TRACE_WARNING( _S("PackColor4::get_packed_argb: Color value found the same as magic num.") );
				}
			}
		}

		// unpack me
		void unpack_me( void ) const
		{
			if( m_unpack[0] == Color4::nan() )
			{
				const float f = 1.0f / 255.0f;
				m_unpack[0] = f * (float) (unsigned char) (m_packed_argb >> 16);
				m_unpack[1] = f * (float) (unsigned char) (m_packed_argb >>  8);
				m_unpack[2] = f * (float) (unsigned char) (m_packed_argb >>  0);
				m_unpack[3] = f * (float) (unsigned char) (m_packed_argb >> 24);
			}
		}

		//@ data

		mutable float	m_unpack[4];
		mutable DWORD	m_packed_argb;
	};

} // namespace xeres
